package tester;

import java.util.ArrayList;
import java.util.Stack;
import returnTypes.FinalValues;
import returnTypes.PointNumber;
import room.Room;
import wall.Wall;

public class RoomTester {
	private Stack<Wall> Solved = new Stack<Wall>();
	private Stack<Wall> Copy = new Stack<Wall>();
	private Stack<PointNumber> Continue = new Stack<PointNumber>();
	private Stack<Integer> Index = new Stack<Integer>();
	private ArrayList<Wall> walls = new ArrayList<Wall>();
	private boolean popping = false;
	private boolean tester = false;
	private final int maxY=FinalValues.getMaxY();
	private final int maxX=FinalValues.getMaxX();
	private int[][][] insideMatrix = new int[maxX][maxY][6];
	private int owner = -1;
	private Room returnRoom;
	
	private void restart(){
		this.setOwner(-1);
		Solved.clear();
		Continue.clear();
		Index.clear();
		walls.clear();
		returnRoom= null;
		popping = false;		
	}
	

	public void setOwner(int owner) {
		this.owner = owner;
	}

    public int getOwner() {
		return owner;
	}

	private void setMatrixValueXY(int x,int y,int value)
	{
		insideMatrix[x][y][0]=value;
	}
	
	private void setMatrixID(int x,int y,int z,int owner)
	{
		insideMatrix[x][y][z]=owner;
	}
	
	private int getMatrixValueXY(int x,int y)
	{
		return insideMatrix[x][y][0];
	}
	
	public boolean getTester()
	{
		return tester;
	}
	
	private void setTrue()
	{
		tester=true;
	}
	
	private void setFalse()
	{
		tester=false;
	}
	
	public int[][][] parseMatrix()
	{
		return insideMatrix;
	}
	
	public void setWalls(ArrayList<Wall> newWalls)
	{
		for(int i=0;i<newWalls.size();i++)
			walls.add(newWalls.get(i));
	}
	
	public void printStack()
	{
		for(int i=0;i<Solved.size();i++)
		{
			System.out.print(i+ ": " + Solved.get(i).toString()+"  ");
		}
		System.out.println();
	}
	
	private PointNumber continues1(Wall currentWall, Wall nextWall)
	{
		PointNumber pn;
		if ( currentWall.getBaseLine().getPoint1().equals(nextWall.getBaseLine().getPoint1()))
		{ pn=new PointNumber(1,1); return pn; }
		if ( currentWall.getBaseLine().getPoint1().equals(nextWall.getBaseLine().getPoint2()))
		{ pn=new PointNumber(1,2); return pn; }
		return new PointNumber(0,0);
	}
	
	private PointNumber continues2(Wall currentWall, Wall nextWall)
	{
		PointNumber pn;
		if ( currentWall.getBaseLine().getPoint2().equals(nextWall.getBaseLine().getPoint1()))
		{ pn=new PointNumber(2,1); return pn; }
		if ( currentWall.getBaseLine().getPoint2().equals(nextWall.getBaseLine().getPoint2()))
		{ pn=new PointNumber(2,2); return pn; }
		return new PointNumber(0,0);
		
	}
	
			
	@SuppressWarnings("unchecked")
	private void solve(Wall wall,PointNumber pn,Integer index)
	{
			Solved.push(wall);
			Continue.push(pn);
			Index.push(index);
		int iterator=0;
		PointNumber pnA=new PointNumber();
		if( Solved.size() != walls.size())
			{
				if ( cycle() )
					{
						createMatrix();
						setTrue();
						for(int i=0; i<walls.size();i++)
						{
							
							if  ( notFound(walls.get(i)) == true )
								if ( isInside( walls.get(i) )== true ) { Solved.push(walls.get(i)); }
								else{ Error1(walls.get(i)); setFalse(); break; } 
						}
						if (getTester()== false) return; else {Copy=(Stack<Wall>)Solved.clone(); PaintInterior(); setTrue();}				
					}else 
						{
							if (popping == false ) index = 0;
							for(int i=index; i<walls.size();i++)
							{
								if  ( notFound(walls.get(i)) == true )
									{
										if ( pn.getNumber2()== 1 ) 
											{ 
												pnA = continues2(wall, walls.get(i)); 
												popping = false;
												if(  pnA.notNull() ) {iterator=i; break;}
											}else
												if ( pn.getNumber2()== 2 ) 
												{ 
													pnA = continues1(wall, walls.get(i)); 
													popping = false;
													if(   pnA.notNull() ){ iterator=i; break;}
												}
									}								
							}
							if ( pnA.notNull() ) { solve(walls.get(iterator),pnA,iterator++); } else {
							Solved.pop();
							Continue.pop();
							Index.pop();
							popping = true;
							solve(Solved.peek(),Continue.peek(),Index.peek());	}
							
						}
			
			}
				else{ 
					if ( cycle() ) {Copy=(Stack<Wall>)Solved.clone(); createMatrix(); PaintInterior(); 
						setTrue(); }
						else{ Error1(Solved.peek()); setFalse(); return;} 
					}
			
	}
	
	private void PaintInterior()
	{
		for(int i=0;i<maxX;i++)
			for(int j=0;j<maxY;j++)
				if (insideMatrix[i][j][0]==0) { if ( isBoxed(i,j) )  handlePoint(0,9,i,j);
	
				}
	}
	
	private boolean notFound(Wall wall)
	{
		for(int i=0; i<Solved.size();i++)
		{
			if (wall.equals(Solved.get(i)))
			return false;
		}
		return true;
	}
	
	public Room createRoom(ArrayList<Wall> walls,int roomID){
		restart();
		setOwner(roomID);
		setWalls(walls);
		PointNumber pn=new PointNumber(1,1);
		solve(walls.get(0),pn,0);	
		if (getTester()==true) returnRoom=new Room(Copy);
		else returnRoom=null;
		return returnRoom;
	}
	
	private void createMatrix(){
		while(Solved.size()!=0)
			breakUpWallandDraw(Solved.pop(),1);	
	
	}
	
	public void printMatrix()
	{
		for(int i=0;i<maxX;i++){
			for(int j=0;j<maxY;j++)
				System.out.print(insideMatrix[i][j][0]);
		System.out.println();
		}
	}
	
	private void breakUpWallandDraw(Wall drawWall,int value){
		
		int x1=drawWall.getBaseLine().getPoint1().getCmX();
		int y1=drawWall.getBaseLine().getPoint1().getCmY();
		int x2=drawWall.getBaseLine().getPoint2().getCmX();
		int y2=drawWall.getBaseLine().getPoint2().getCmY();
		line(x1,y1,x2,y2,value);
	}
	
	private void line(int x,int y,int x2, int y2, int value) {
	    int w = x2 - x ;
	    int h = y2 - y ;
	    int dx1 = 0, dy1 = 0, dx2 = 0, dy2 = 0 ;
	    if (w<0) dx1 = -1 ; else if (w>0) dx1 = 1 ;
	    if (h<0) dy1 = -1 ; else if (h>0) dy1 = 1 ;
	    if (w<0) dx2 = -1 ; else if (w>0) dx2 = 1 ;
	    int longest = Math.abs(w) ;
	    int shortest = Math.abs(h) ;
	    if (!(longest>shortest)) {
	        longest = Math.abs(h) ;
	        shortest = Math.abs(w) ;
	        if (h<0) dy2 = -1 ; else if (h>0) dy2 = 1 ;
	        dx2 = 0 ;            
	    }
	    int numerator = longest >> 1 ;
	    for (int i=0;i<=longest;i++) {
	    	handlePoint(getMatrixValueXY(x,y),value,x,y);
	        numerator += shortest ;
	        if (!(numerator<longest)) {
	            numerator -= longest ;
	            x += dx1 ;
	            y += dy1 ;
	        } else {
	            x += dx2 ;
	            y += dy2 ;
	        }
	    }
    } 
	
	private void handlePoint(int oldValue, int newValue,int x, int y) {
		switch(oldValue){
		case 0:  setMatrixValueXY(x,y, newValue); setMatrixID(x,y,1,getOwner()); break;
		case 1:  if( newValue==1 ) setMatrixValueXY(x,y, 2); setMatrixID(x,y,2,getOwner()); break;
		case 2:  if( newValue==1 ) setMatrixValueXY(x,y, 3); setMatrixID(x,y,3,getOwner()); break;	
		case 3:  if( newValue==1 ) setMatrixValueXY(x,y, 4); setMatrixID(x,y,4,getOwner()); break;		
		case 4:  if( newValue==1 ) setMatrixValueXY(x,y, 5); setMatrixID(x,y,5,getOwner()); break;	
		case 6:  if( newValue==9 ) break;
		case 9:  Error2(); break;
		}
		
	}
	
	private void Error2()
	{
		System.out.println("Overlapping Lines");
		System.exit(2);
	}

	private boolean isInside(Wall testWall)
	{
		breakUpWallandDraw(testWall,6);
		for(int i=0;i<maxX;i++)
			for(int j=0;j<maxY;j++)
				if(insideMatrix[i][j][0]==6){ if ( isBoxed(i,j))  {} else return false; }
		return true;
	}
	
	private boolean hasUpper(int x,int y)
	{
		for(int i=0; i<x;i++)
			if (insideMatrix[i][y][0]==1 || insideMatrix[i][y][0]==4 || insideMatrix[i][y][0]==5 || insideMatrix[i][y][0]==6) return true;
		return false;
	}
	private boolean hasLower(int x,int y)
	{
		for(int i=x; i<maxY;i++)
			if (insideMatrix[i][y][0]==1 || insideMatrix[i][y][0]==4 || insideMatrix[i][y][0]==5 || insideMatrix[i][y][0]==6) return true;
		return false;
	}
	private boolean hasLeft(int x,int y)
	{
		for(int i=0; i<y;i++)
			if (insideMatrix[x][i][0]==1 || insideMatrix[x][i][0]==4 || insideMatrix[x][i][0]==5 || insideMatrix[x][i][0]==6) return true;
		return false;
	}
	private boolean hasRight(int x,int y)
	{
		for(int i=y; i<maxX;i++)
			if (insideMatrix[x][i][0]==1 || insideMatrix[x][i][0]==4 || insideMatrix[x][i][0]==5 || insideMatrix[x][i][0]==6) return true;
		return false;
	}
	
	private boolean isBoxed(int i, int j)
	{
		if ( hasUpper(i,j) && hasLower(i,j) && hasLeft(i,j) && hasRight(i,j) )  return true; else return false; 
	}	
	
	
	private void Error1(Wall wall)
	{
		System.out.println("Error at "+ wall.toString());
		System.exit(1);
	}
	
	private boolean cycle()
	{
		if ( Solved.size()>=3 ){
			
			PointNumber pn=continues1(Solved.get(0),Solved.get(1));
				if ( pn.getNumber1()==0 ) pn=continues2(Solved.get(0),Solved.get(1));
			for(int i=1; i<Solved.size()-1; i++)
			{
				if ( pn.getNumber2() == 1)
				pn=continues2(Solved.get(i),Solved.get(i+1)); else
				if ( pn.getNumber2() == 2) 
				pn=continues1(Solved.get(i),Solved.get(i+1)); else
				if ( pn.getNumber2() == 0) return false;
			}
				
				if ( pn.getNumber2() == 1)
				pn=continues2(Solved.lastElement(),Solved.firstElement()); else
				if ( pn.getNumber2() == 2) 
				pn=continues1(Solved.lastElement(),Solved.firstElement()); else
				if ( pn.getNumber2() == 0) return false;
				
				if ( pn.getNumber1() != 0 ) return true;
		}
		return false;
	}
	
		
}
